#ifndef _COMPILATION_PROGRAM_H_
#define _COMPILATION_PROGRAM_H_

#include <compilation/Forward.h>

#include <list>
#include <set>
#include <iostream>
#include <functional>

#include <compilation/Table.h>
#include <compilation/instruction/Instruction.h>
#include <compilation/Function.h>


int getTokenIdentifier(const char* szText);

#ifndef NDEBUG
namespace compilation
{ extern bool display_info_during_parsing; }
# define SET_DEBUG_FOR_PARSING() \
  compilation::display_info_during_parsing = true
# define UNSET_DEBUG_FOR_PARSING() \
  compilation::display_info_during_parsing = false
#else
# define SET_DEBUG_FOR_PARSING()
# define UNSET_DEBUG_FOR_PARSING()
#endif

namespace compilation
{

class Program
{
private:

  std::set<Function> m_functions;
  Scope m_globalScope;

public:

  typedef ProgramParseContext ParseContext;
  friend class ProgramParseContext;

  Program(void) {}

  std::set<Function>& get_functions(void)
  { return m_functions; }

  std::set<Function> const& get_functions(void) const
  { return m_functions; }

  void setScopes(void);

  void print(std::ostream& out) const;

  void printWithWorkList(std::ostream& out) const;

  void computeDominators(void);

  void computeDominationFrontiers(void);

  void insertPhiFunctions(void);

  void exportToDot(std::string const& filename);

  void renameSSA(void);

private:

  void insertPhiExpressions(
      Function& function,
      std::vector<LabelInstruction*> const& labels);
};

class ProgramParseContext
{
private:

  friend int ::getTokenIdentifier(const char* szText);

  class ParseState
  {
  private:

    enum Type
    {
      TUndefined,
      TIfThen,
      TIfElse,
      TDo,
      TWhile,
      TFor,
      TBlock
    };

    Type m_type;
    VirtualInstruction* m_reference;
    VirtualInstruction* m_endThen;

  public:

    ParseState(void)
      : m_type(TUndefined), m_reference(nullptr), m_endThen(nullptr) {}

    ParseState(const ParseState& source)
      :  m_type(source.m_type), m_reference(source.m_reference), m_endThen(source.m_endThen) {}

    GotoInstruction& setIf(IfInstruction& ifInstruction, Function& function)
    {
      assert(m_type == TUndefined);
      m_type = TIfThen;
      m_reference = &ifInstruction;
      return function.setThen(ifInstruction);
    }

    GotoInstruction& setElse(Function& function, VirtualInstruction& viEndThen)
    {
      assert(m_type == TIfThen);
      m_type = TIfElse;
      m_endThen = &viEndThen;
      return function.setElse(*instruction_cast<TIf>(m_reference));
    }

    LabelInstruction& setDoLoop(LabelInstruction& label)
    {
      assert(m_type == TUndefined);
      m_type = TDo;
      m_reference = &label;
      return label;
    }

    LabelInstruction& setWhileLoop(LabelInstruction& label)
    {
      assert(m_type == TUndefined);
      m_type = TWhile;
      m_reference = &label;
      return label;
    }

    LabelInstruction& getLoopReference() const
    {
      assert((m_type >= TDo || m_type <= TFor));
      m_reference->checkType<LabelInstruction>();
      return *instruction_cast<TLabel>(m_reference);
    }

    VirtualInstruction& getEndThen() const
    { assert(m_endThen); return *m_endThen; }

    IfInstruction& getCondition() const
    { return *instruction_cast<TIf>(m_reference); }

    EnterBlockInstruction& setBlock(EnterBlockInstruction& ebiEnterBlock)
    {
      assert(m_type == TUndefined);
      m_type = TBlock;
      m_reference = &ebiEnterBlock;
      return ebiEnterBlock;
    }

    EnterBlockInstruction& getBlock(void)
    {
      assert(m_type == TBlock);
      return *instruction_cast<TEnterBlock>(m_reference);
    }
  };

  Program* m_currentProgram;
  Function* m_function;
  VirtualInstruction* m_current;
  std::list<ParseState> m_states;
  Scope m_scope;

public:

  ProgramParseContext(void)
    : m_currentProgram(nullptr), m_function(nullptr), m_current(nullptr) {}

  ProgramParseContext(Program& pProgram)
    : m_currentProgram(&pProgram)
    , m_function(nullptr)
    , m_current(nullptr)
    , m_scope(pProgram.m_globalScope) {}

  void setProgram(Program& program)
  {
    assert(!m_currentProgram);
    m_currentProgram = &program;
    m_scope = program.m_globalScope;
  }

  void clear(void)
  {
    m_currentProgram = nullptr; m_function = nullptr; m_current = nullptr;
    m_states.clear(); m_scope = Scope();
  }

  Scope& scope(void)
  { return m_scope; }

  void addNewReturnInstruction(ReturnInstruction* returnInstruction)
  {
    assert(m_function && m_current);
    m_function->addNewInstructionAfter(returnInstruction, *m_current);
    m_current = nullptr;
  }

  void addNewInstruction(VirtualInstruction* instruction)
  {
    assert(m_function && m_current);
    m_function->addNewInstructionAfter(instruction, *m_current);
    m_current = instruction;
  }

  void addDeclarationStatement(VirtualType* type, const std::string& name)
  { m_scope.addDeclaration(name, type); }

  VirtualInstruction& getLastInstruction() const
  { assert(m_current); return *m_current; }

  void pushDoLoop(void)
  {
    GotoInstruction* gotoPoint = new GotoInstruction();
    m_function->addNewInstructionAfter(gotoPoint, *m_current);
    gotoPoint->setBeforeLabel();
    LabelInstruction* label = new LabelInstruction();
    m_function->addNewInstructionAfter(label, *gotoPoint);
    m_states.push_back(ParseState());
    m_current = &m_states.back().setDoLoop(*label);
  }

  VirtualInstruction& closeDoLoop(VirtualInstruction& last, VirtualExpression* expression)
  {
    assert(m_current == &last);
    LabelInstruction& label = m_states.back().getLoopReference();
    m_states.pop_back();
    IfInstruction* condition = new IfInstruction();
    m_function->addNewInstructionAfter(condition, *m_current);
    condition->setExpression(expression);
    GotoInstruction& thenPoint = m_function->setThen(*condition);
    GotoInstruction* loopPoint = new GotoInstruction();
    m_function->addNewInstructionAfter(loopPoint, thenPoint);
    loopPoint->setLoop();
    loopPoint->connectToLabel(label);
    return *(m_current = &m_function->setElse(*condition));
  }

  void pushWhileLoop(VirtualExpression* expression)
  {
    LabelInstruction* label = new LabelInstruction();
    m_function->addNewInstructionAfter(label, *m_current);
    m_states.push_back(ParseState());
    m_current = &m_states.back().setWhileLoop(*label);
    IfInstruction* condition = new IfInstruction();
    m_function->addNewInstructionAfter(condition, *m_current);
    condition->setExpression(expression);
    m_current = &m_function->setThen(*condition);
  }

  VirtualInstruction& closeWhileLoop(VirtualInstruction& last)
  {
    assert(m_current == &last);
    LabelInstruction& label = m_states.back().getLoopReference();
    GotoInstruction* loopPoint = new GotoInstruction();

    m_function->addNewInstructionAfter(loopPoint, last);
    loopPoint->setLoop();
    loopPoint->connectToLabel(label);
    m_states.pop_back();

    IfInstruction& condition =
        *instruction_cast<TIf>(label.getSNextInstruction());
    return *(m_current = &m_function->setElse(condition));
  }

  void pushIfThen(VirtualExpression* expression)
  {
    IfInstruction* condition = new IfInstruction();
    m_function->addNewInstructionAfter(condition, *m_current);
    condition->setExpression(expression);
    m_states.push_back(ParseState());
    m_current = &m_states.back().setIf(*condition, *m_function);
  }

  void setToIfElse(VirtualInstruction& last)
  {
    assert(m_current == &last);
    m_current = &m_states.back().setElse(*m_function, last);
  }

  VirtualInstruction& closeIfThen(VirtualInstruction& last)
  {
    assert(m_current == &last);
    GotoInstruction* thenLabel = new GotoInstruction();
    m_function->addNewInstructionAfter(thenLabel, *m_current);
    thenLabel->setBeforeLabel();
    LabelInstruction* label = new LabelInstruction();
    m_function->addNewInstructionAfter(label, *thenLabel);
    GotoInstruction& giElse = m_function->setElse(m_states.back().getCondition());
    GotoInstruction* connectToLabel = new GotoInstruction();
    m_function->addNewInstructionAfter(connectToLabel, giElse);
    connectToLabel->setBeforeLabel();
    connectToLabel->connectToLabel(*label);
    m_states.pop_back();
    m_current = label;
    return *label;
  }

  VirtualInstruction& closeIfElse(VirtualInstruction& lastThen, VirtualInstruction& lastElse)
  {
    assert(m_current == &lastElse && &m_states.back().getEndThen() == &lastThen);
    GotoInstruction* elseLabel = new GotoInstruction();
    m_function->addNewInstructionAfter(elseLabel, lastElse);
    elseLabel->setBeforeLabel();
    LabelInstruction* label = new LabelInstruction();
    m_function->addNewInstructionAfter(label, *elseLabel);
    GotoInstruction* connectToLabel = new GotoInstruction();
    m_function->addNewInstructionAfter(connectToLabel, lastThen);
    connectToLabel->setBeforeLabel();
    connectToLabel->connectToLabel(*label);
    m_states.pop_back();
    m_current = label;
    return *label;
  }

  void pushBlock(void)
  {
    EnterBlockInstruction* block = new EnterBlockInstruction();
    m_function->addNewInstructionAfter(block, *m_current);
    m_scope.push();
    block->setScope(m_scope);
    m_states.push_back(ParseState());
    m_current = &m_states.back().setBlock(*block);
  }

  VirtualInstruction& closeBlock(VirtualInstruction& last)
  {
    assert(m_current == &last);
    EnterBlockInstruction& enter = m_states.back().getBlock();
    m_states.pop_back();
    ExitBlockInstruction* pebiExit = new ExitBlockInstruction();
    pebiExit->setScope(enter.scope());
    m_function->addNewInstructionAfter(pebiExit, last);
    m_current = pebiExit;
    return *pebiExit;
  }

  Function& addFunction(const std::string& functionName)
  {
    assert(m_currentProgram);
    return const_cast<Function&>(
          *(m_currentProgram->m_functions.insert(
              Function(functionName, m_currentProgram->m_globalScope)).first));
  }

  void pushFunction(const std::string& functionName)
  {
    assert(m_currentProgram && !m_current);
    m_function = &const_cast<Function&>(
          *(m_currentProgram->m_functions.find(
              Function(functionName, m_currentProgram->m_globalScope))));
    m_scope = m_currentProgram->m_globalScope;

    EnterBlockInstruction* block = new EnterBlockInstruction();
    m_function->addFirstInstruction(block);

    m_scope.push();
    block->setScope(m_scope);

    m_states.push_back(ParseState());

    m_current = &m_states.back().setBlock(*block);
  }

  void popFunction(void)
  {
    assert(m_currentProgram && m_function);
    EnterBlockInstruction& enter = m_states.back().getBlock();
    m_states.pop_back();
    ExitBlockInstruction* pebiExit = new ExitBlockInstruction();
    pebiExit->setScope(enter.scope());
    m_function->addNewInstruction(pebiExit);
    m_function = nullptr;
    m_scope = m_currentProgram->m_globalScope;
    m_current = nullptr;
  }

  void setEmptyFunction(const std::string& functionName)
  {
    assert(m_currentProgram && !m_current);
    m_function = &const_cast<Function&>(
          *(m_currentProgram->m_functions.find(
              Function(functionName, m_currentProgram->m_globalScope))));
    EnterBlockInstruction* block = new EnterBlockInstruction();
    m_function->addFirstInstruction(block);
    ExitBlockInstruction* pebiExit = new ExitBlockInstruction();
    m_function->addNewInstruction(pebiExit);
    m_function = nullptr;
  }

  void addGlobalDeclarationStatement(VirtualType* type, const std::string& name)
  {
    assert(m_currentProgram);
    m_currentProgram->m_globalScope.addDeclaration(name, type);
  }

  const std::set<Function>& functions() const
  { return m_currentProgram->m_functions; }
};

} // compilation

#endif // _COMPILATION_PROGRAM_H_
